#include "woptions/woptions.h"
#include "test.h"

namespace {

std::string check_single_char(std::string s)
{
	woptions::utf8_iterator it(s.begin(),s.end());
	woptions::utf8_iterator end(s.end(),s.end());
	BOOST_CHECK( it.is_valid_utf8() );
	BOOST_CHECK_EQUAL( std::distance(it,end), 1);
	return it.is_valid_utf8() ? *it : "";
}

std::string check_invalid_char(std::string s)
{
	woptions::utf8_iterator it(s.begin(),s.end());
	woptions::utf8_iterator end(s.end(),s.end());
	BOOST_CHECK( !it.is_valid_utf8() );
	BOOST_CHECK_EQUAL( std::distance(it,end), 1);
	return it.is_valid_utf8() ? "" : *it;
}

void check_invalid(std::string s)
{
	size_t n = s.length();
	woptions::utf8_iterator it(s.begin(),s.end());
	woptions::utf8_iterator end(s.end(),s.end());
	
	for(int i=0;it!=end;++it,++i,--n)
	{
		BOOST_CHECK_EQUAL( *it, s.substr(i,1) );
		BOOST_CHECK( !it.is_valid_utf8() );
	}
	BOOST_CHECK_EQUAL( n, size_t(0) );
}



void check_invalid_chars(std::string s, int n)
{
	woptions::utf8_iterator it(s.begin(),s.end());
	woptions::utf8_iterator end(s.end(),s.end());
	for(;it!=end;++it,--n)
	{
		BOOST_CHECK( !it.is_valid_utf8() );
	}
	BOOST_CHECK_EQUAL( n, 0 );
}

void check_invalid_continuation(std::string s)
{
	BOOST_REQUIRE_EQUAL(s.length()%2,size_t(0));
	
	size_t n = s.length()/2;
	woptions::utf8_iterator it(s.begin(),s.end());
	woptions::utf8_iterator end(s.end(),s.end());
	
	for(int i=0;it!=end;--n)
	{
		BOOST_CHECK_EQUAL( *it, s.substr(i++,1) );
		BOOST_CHECK( !it.is_valid_utf8() );
		++it;
		
		BOOST_CHECK_EQUAL( *it, s.substr(i++,1) );
		BOOST_CHECK(  it.is_valid_utf8() );
		++it;
	}
	BOOST_CHECK_EQUAL( n, size_t(0) );
}

} // namespace

BOOST_AUTO_TEST_CASE( test_utf8_kosme )
{
	std::string s("\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5");
	
	woptions::utf8_iterator it(s.begin(),s.end());
	woptions::utf8_iterator end(s.end(),s.end());
	
	BOOST_CHECK_EQUAL( *it++, "\xce\xba" );
	BOOST_CHECK_EQUAL( *it++, "\xe1\xbd\xb9" );
	BOOST_CHECK_EQUAL( *it++, "\xcf\x83" );
	BOOST_CHECK_EQUAL( *it++, "\xce\xbc" );
	BOOST_CHECK_EQUAL( *it++, "\xce\xb5" );
	BOOST_CHECK( it == end );
}

BOOST_AUTO_TEST_CASE( test_utf8_first )
{
	std::string s1(1, char(0));
	std::string s2("\xc2\x80");
	std::string s3("\xe0\xa0\x80");
	std::string s4("\xf0\x90\x80\x80");
	std::string s5("\xf8\x88\x80\x80\x80");
	std::string s6("\xfc\x84\x80\x80\x80\x80");
	
	BOOST_CHECK_EQUAL( check_single_char(s1), s1);
	BOOST_CHECK_EQUAL( check_single_char(s2), s2);
	BOOST_CHECK_EQUAL( check_single_char(s3), s3);
	BOOST_CHECK_EQUAL( check_single_char(s4), s4);
	BOOST_CHECK_EQUAL( check_single_char(s5), s5);
	BOOST_CHECK_EQUAL( check_single_char(s6), s6);
}

BOOST_AUTO_TEST_CASE( test_utf8_last )
{
	std::string s1("\x7f");
	std::string s2("\xdf\xbf");
	std::string s3("\xef\xbf\xbd");
	std::string s4("\xf7\xbf\xbf\xbf");
	std::string s5("\xfb\xbf\xbf\xbf\xbf");
	std::string s6("\xfd\xbf\xbf\xbf\xbf\xbf");
	
	BOOST_CHECK_EQUAL( check_single_char(s1), s1);
	BOOST_CHECK_EQUAL( check_single_char(s2), s2);
	BOOST_CHECK_EQUAL( check_single_char(s3), s3);
	BOOST_CHECK_EQUAL( check_single_char(s4), s4);
	BOOST_CHECK_EQUAL( check_single_char(s5), s5);
	BOOST_CHECK_EQUAL( check_single_char(s6), s6);
}

BOOST_AUTO_TEST_CASE( test_utf8_boundary )
{
	std::string s1("\xed\x9f\xbf");
	std::string s2("\xee\x80\x80");
	std::string s3("\xef\xbf\xbd");
	std::string s4("\xf4\x8f\xbf\xbf");
	std::string s5("\xf4\x90\x80\x80");
	
	BOOST_CHECK_EQUAL( check_single_char(s1), s1);
	BOOST_CHECK_EQUAL( check_single_char(s2), s2);
	BOOST_CHECK_EQUAL( check_single_char(s3), s3);
	BOOST_CHECK_EQUAL( check_single_char(s4), s4);
	BOOST_CHECK_EQUAL( check_single_char(s5), s5);
}

BOOST_AUTO_TEST_CASE( test_utf8_unexpected_continuation )
{
	std::string s1("\x80");
	std::string s2("\xBF");
	std::string s3("\x80\xBF");
	std::string s4("\x80\xBF\x80");
	std::string s5("\x80\xBF\x80\xBF");
	std::string s6("\x80\xBF\x80\xBF\x80");
	std::string s7("\x80\xBF\x80\xBF\x80\xBF");
	std::string s8("\x80\xBF\x80\xBF\x80\xBF\x80");
	std::string s9(
		"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F"
		"\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F"
		"\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF"
		"\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF");
	
	check_invalid(s1);
	check_invalid(s2);
	check_invalid(s3);
	check_invalid(s4);
	check_invalid(s5);
	check_invalid(s6);
	check_invalid(s7);
	check_invalid(s8);
	check_invalid(s9);
}

BOOST_AUTO_TEST_CASE( test_utf8_no_continuation )
{
	std::string s1(
		"\xC0 \xC1 \xC2 \xC3 \xC4 \xC5 \xC6 \xC7 \xC8 \xC9 \xCA \xCB \xCC \xCD \xCE \xCF "
		"\xD0 \xD1 \xD2 \xD3 \xD4 \xD5 \xD6 \xD7 \xD8 \xD9 \xDA \xDB \xDC \xDD \xDE \xDF ");
	std::string s2(
		"\xE0 \xE1 \xE2 \xE3 \xE4 \xE5 \xE6 \xE7 \xE8 \xE9 \xEA \xEB \xEC \xED \xEE \xEF ");
	std::string s3(
		"\xF0 \xF1 \xF2 \xF3 \xF4 \xF5 \xF6 \xF7 ");
	std::string s4(
		"\xF8 \xF9 \xFA \xFB ");
	std::string s5(
		"\xFC \xFD ");
	
	check_invalid_continuation(s1);
	check_invalid_continuation(s2);
	check_invalid_continuation(s3);
	check_invalid_continuation(s4);
	check_invalid_continuation(s5);
}

BOOST_AUTO_TEST_CASE( test_utf8_missing_continuation )
{
	std::string s1("\xC0");
	std::string s2("\xE0\x80");
	std::string s3("\xF0\x80\x80");
	std::string s4("\xF8\x80\x80\x80");
	std::string s5("\xFC\x80\x80\x80\x80");
	std::string s6("\xDF");
	std::string s7("\xEF\xBF");
	std::string s8("\xF7\xBF\xBF");
	std::string s9("\xFB\xBF\xBF\xBF");
	std::string s10("\xFD\xBF\xBF\xBF\xBF");
	
	BOOST_CHECK_EQUAL( check_invalid_char( s1), s1 );
	BOOST_CHECK_EQUAL( check_invalid_char( s2), s2 );
	BOOST_CHECK_EQUAL( check_invalid_char( s3), s3 );
	BOOST_CHECK_EQUAL( check_invalid_char( s4), s4 );
	BOOST_CHECK_EQUAL( check_invalid_char( s5), s5 );
	BOOST_CHECK_EQUAL( check_invalid_char( s6), s6 );
	BOOST_CHECK_EQUAL( check_invalid_char( s7), s7 );
	BOOST_CHECK_EQUAL( check_invalid_char( s8), s8 );
	BOOST_CHECK_EQUAL( check_invalid_char( s9), s9 );
	BOOST_CHECK_EQUAL( check_invalid_char(s10),s10 );
}

BOOST_AUTO_TEST_CASE( test_utf8_missing_continuation_all )
{
	std::string s1(
		"\xC0"
		"\xE0\x80"
		"\xF0\x80\x80"
		"\xF8\x80\x80\x80"
		"\xFC\x80\x80\x80\x80"
		"\xDF"
		"\xEF\xBF"
		"\xF7\xBF\xBF"
		"\xFB\xBF\xBF\xBF"
		"\xFD\xBF\xBF\xBF\xBF");
	
	check_invalid_chars(s1,10);
}

BOOST_AUTO_TEST_CASE( test_utf8_impossible_bytes )
{
	std::string s1("\xFE");
	std::string s2("\xFF");
	std::string s3("\xFE\xFE\xFF\xFF");
	
	check_invalid(s1);
	check_invalid(s2);
	check_invalid(s3);
}

BOOST_AUTO_TEST_CASE( test_utf8_overlong_sequence_slash )
{
	std::string s1("\xC0\xAF");
	std::string s2("\xE0\x80\xAF");
	std::string s3("\xF0\x80\x80\xAF");
	std::string s4("\xF8\x80\x80\x80\xAF");
	std::string s5("\xFC\x80\x80\x80\x80\xAF");
	
	BOOST_CHECK_EQUAL( check_invalid_char(s1), s1 );
	BOOST_CHECK_EQUAL( check_invalid_char(s2), s2 );
	BOOST_CHECK_EQUAL( check_invalid_char(s3), s3 );
	BOOST_CHECK_EQUAL( check_invalid_char(s4), s4 );
	BOOST_CHECK_EQUAL( check_invalid_char(s5), s5 );
}

BOOST_AUTO_TEST_CASE( test_utf8_overlong_sequence_max )
{
	std::string s1("\xC1\xBF");
	std::string s2("\xE0\x9F\xBF");
	std::string s3("\xF0\x8F\xBF\xBF");
	std::string s4("\xF8\x87\xBF\xBF\xBF");
	std::string s5("\xFC\x83\xBF\xBF\xBF\xBF");
	
	BOOST_CHECK_EQUAL( check_invalid_char(s1), s1 );
	BOOST_CHECK_EQUAL( check_invalid_char(s2), s2 );
	BOOST_CHECK_EQUAL( check_invalid_char(s3), s3 );
	BOOST_CHECK_EQUAL( check_invalid_char(s4), s4 );
	BOOST_CHECK_EQUAL( check_invalid_char(s5), s5 );
}

BOOST_AUTO_TEST_CASE( test_utf8_overlong_sequence_null )
{
	std::string s1("\xC0\x80");
	std::string s2("\xE0\x80\x80");
	std::string s3("\xF0\x80\x80\x80");
	std::string s4("\xF8\x80\x80\x80\x80");
	std::string s5("\xFC\x80\x80\x80\x80\x80");
	
	BOOST_CHECK_EQUAL( check_invalid_char(s1), s1 );
	BOOST_CHECK_EQUAL( check_invalid_char(s2), s2 );
	BOOST_CHECK_EQUAL( check_invalid_char(s3), s3 );
	BOOST_CHECK_EQUAL( check_invalid_char(s4), s4 );
	BOOST_CHECK_EQUAL( check_invalid_char(s5), s5 );
}

BOOST_AUTO_TEST_CASE( test_utf8_illegal_code_single_surrogate )
{
	std::string s1("\xed\xa0\x80");
	std::string s2("\xed\xad\xbf");
	std::string s3("\xed\xae\x80");
	std::string s4("\xed\xaf\xbf");
	std::string s5("\xed\xb0\x80");
	std::string s6("\xed\xbe\x80");
	std::string s7("\xed\xbf\xbf");
	
	BOOST_CHECK_EQUAL( check_invalid_char(s1), s1 );
	BOOST_CHECK_EQUAL( check_invalid_char(s2), s2 );
	BOOST_CHECK_EQUAL( check_invalid_char(s3), s3 );
	BOOST_CHECK_EQUAL( check_invalid_char(s4), s4 );
	BOOST_CHECK_EQUAL( check_invalid_char(s5), s5 );
	BOOST_CHECK_EQUAL( check_invalid_char(s6), s6 );
	BOOST_CHECK_EQUAL( check_invalid_char(s7), s7 );
}

BOOST_AUTO_TEST_CASE( test_utf8_illegal_code_paired_surrogate )
{
	std::string s1("\xed\xa0\x80\xed\xb0\x80");
	std::string s2("\xed\xa0\x80\xed\xbf\xbf");
	std::string s3("\xed\xad\xbf\xed\xb0\x80");
	std::string s4("\xed\xad\xbf\xed\xbf\xbf");
	std::string s5("\xed\xae\x80\xed\xb0\x80");
	std::string s6("\xed\xae\x80\xed\xbf\xbf");
	std::string s7("\xed\xaf\xbf\xed\xb0\x80");
	std::string s8("\xed\xaf\xbf\xed\xbf\xbf");
	
	check_invalid_chars(s1,2);
	check_invalid_chars(s2,2);
	check_invalid_chars(s3,2);
	check_invalid_chars(s4,2);
	check_invalid_chars(s5,2);
	check_invalid_chars(s6,2);
	check_invalid_chars(s7,2);
	check_invalid_chars(s8,2);
}

BOOST_AUTO_TEST_CASE( test_utf8_illegal_code )
{
	std::string s1("\xef\xbf\xbe");
	std::string s2("\xef\xbf\xbf");
	
	BOOST_CHECK_EQUAL( check_invalid_char(s1), s1 );
	BOOST_CHECK_EQUAL( check_invalid_char(s2), s2 );
}

BOOST_AUTO_TEST_CASE( test_utf8_skip_iterator )
{
	std::string s1("[\xff]");
	
	woptions::utf8_skip_iterator begin(s1.begin(),s1.end());
	woptions::utf8_skip_iterator end(s1.end(),s1.end());
	
	BOOST_CHECK_EQUAL(*begin++,"[");
	BOOST_CHECK_EQUAL(*begin++,"]");
	BOOST_CHECK( begin == end );
}

BOOST_AUTO_TEST_CASE( test_utf8_flags )
{
	bool a = false;
	bool b = false;
	
	woptions::basic::Options<char,woptions::utf8_traits> opts;
	opts.option("-\xce\xba","","desc",woptions::bind_option_value(a,true));
	opts.option("-\xe1\xbd\xb9","","desc",woptions::bind_option_value(b,true));
	
	std::vector<std::string> args(1);
	args[0] = "-\xce\xba\xe1\xbd\xb9";
	
	BOOST_CHECK_NO_THROW( opts.parse(args) );
	BOOST_CHECK_EQUAL( a, true );
	BOOST_CHECK_EQUAL( b, true );
}
